package site.https;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;

public class HttpsProxyServer {

    public static void main(String[] args) throws Exception {

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 6000)
                    .childHandler(new ProxyServiceInit());

            ChannelFuture f = b.bind(8090).sync();
            f.channel().closeFuture().sync();
        } finally {
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static class ProxyServiceInit extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel channel) throws Exception {
            ChannelPipeline p = channel.pipeline();
            p.addLast("http-server-codec", new HttpServerCodec());
            p.addLast("http-service", new HttpService());
        }
    }

    public static class HttpService extends SimpleChannelInboundHandler<HttpObject> {

        //保留全局ctx
        private ChannelHandlerContext ctx;
        //创建一会用于连接web服务器的	Bootstrap
        private Bootstrap b = new Bootstrap();

        //channelActive方法中将ctx保留为全局变量
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            super.channelActive(ctx);
            this.ctx = ctx;
        }

        //Complete方法中刷新数据
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            ctx.flush();
        }


        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject msg) throws Exception {
            if (msg instanceof HttpRequest) {
                //转成 HttpRequest
                HttpRequest req = (HttpRequest) msg;

                HttpMethod method = req.method();    //获取请求方式，http的有get post ...， https的是 CONNECT
                String headerHost = req.headers().get("Host");    //获取请求头中的Host字段
                String host = "";
                int port = 80;                                    //端口默认80
                String[] split = headerHost.split(":");            //可能有请求是 host:port的情况，
                host = split[0];
                if (split.length > 1) {
                    port = Integer.valueOf(split[1]);
                }
                Promise<Channel> promise = createPromise(host, port);    //根据host和port创建连接到服务器的连接

				/*
				根据是http还是http的不同，为promise添加不同的监听器
				*/
                if (method.equals(HttpMethod.CONNECT)) {
                    //如果是https的连接
                    promise.addListener(new FutureListener<Channel>() {
                        @Override
                        public void operationComplete(Future<Channel> channelFuture) throws Exception {
                            //首先向浏览器发送一个200的响应，证明已经连接成功了，可以发送数据了
                            FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));
                            //向浏览器发送同意连接的响应，并在发送完成后移除httpcode和httpservice两个handler
                            ctx.writeAndFlush(resp).addListener(new ChannelFutureListener() {
                                @Override
                                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                                    ChannelPipeline p = ctx.pipeline();
                                    p.remove("http-server-codec");
                                    p.remove("http-service");
                                }
                            });
                            ChannelPipeline p = ctx.pipeline();
                            //将客户端channel添加到转换数据的channel，（这个NoneHandler是自己写的）
                            p.addLast(new NoneHandler(channelFuture.getNow()));
                        }
                    });
                } else {
                    //如果是http连接，首先将接受的请求转换成原始字节数据
                    EmbeddedChannel em = new EmbeddedChannel(new HttpRequestEncoder());
                    em.writeOutbound(req);
                    final Object o = em.readOutbound();
                    em.close();
                    promise.addListener(new FutureListener<Channel>() {
                        @Override
                        public void operationComplete(Future<Channel> channelFuture) throws Exception {
                            //移除	http-server-codec	http-service 并添加	NoneHandler，并向服务器发送请求的byte数据
                            ChannelPipeline p = ctx.pipeline();
                            p.remove("http-server-codec");
                            p.remove("http-service");
                            //添加handler
                            p.addLast(new NoneHandler(channelFuture.getNow()));
                            channelFuture.get().writeAndFlush(o);
                        }
                    });
                }
            } else {
                ReferenceCountUtil.release(msg);
            }
        }


        //根据host和端口，创建一个连接web的连接
        private Promise<Channel> createPromise(String host, int port) {
            final Promise<Channel> promise = ctx.executor().newPromise();

            b.group(ctx.channel().eventLoop())
                    .channel(NioSocketChannel.class)
                    .remoteAddress(host, port)
                    .handler(new NoneHandler(ctx.channel()))
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                    .connect()
                    .addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture channelFuture) throws Exception {
                            if (channelFuture.isSuccess()) {
                                promise.setSuccess(channelFuture.channel());
                            } else {
                                ctx.close();
                                channelFuture.cancel(true);
                            }
                        }
                    });
            return promise;
        }


    }


    public static class NoneHandler extends ChannelInboundHandlerAdapter {

        private Channel outChannel;

        public NoneHandler(Channel outChannel) {
            this.outChannel = outChannel;
        }


        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //System.out.println("交换数据");
            outChannel.write(msg);
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            outChannel.flush();
        }
    }


}
